CodePipelineでアカウントをまたいだパイプラインを作成してみる
こんにちは。かたいなかです。
最近CodePipelineを使用していて、ステージング環境と本番環境をまたいだパイプラインなどで、アカウントをまたいだパイプラインを構築したくなることがありました。 そこで、今回はそのときに試したアカウントをまたいだパイプラインを構築する方法をご紹介します。
今回の構成
今回は以下のような構成を構築します
開発環境用のアカウントにあるCodeCommitのリポジトリでの変更を契機に本番アカウントでパイプラインを実行する構成です。CodeCommitだけ別アカウントで少し変な構成に見えますが、devブランチは開発環境にデプロイ、masterブランチは本番環境にデプロイされるようなケースの本番環境へのデプロイの部分をイメージした構成です。
CodePipeline/CodeBuildはS3に暗号化されたファイルを置くことでアーティファクトをやり取りしています。 そのため、こういったアカウントをまたいだパイプラインを構築するには以下のような設定がされている必要があります。
- CodePipeline/CodeBuildで暗号化キーが指定されていること
- CodePipelineで開発環境のCodeCommitにアクセスするアクションについて、開発アカウント側のCodeCommitアクセス用IAMロールが指定されていること。
- 開発アカウント側のCodeCommitアクセス用IAMロールでCodeCommitのリポジトリにアクセスできること
- 本番環境アカウントのCodePipelineのサービスロールからリソース側のアカウントのCodeCommitアクセス用ロールにAssumeRoleできること
- アーティファクト用S3バケットおよびそのオブジェクトの暗号化に使うKMSの暗号化キーに対して適切なロールにアクセス権が与えられていること
- CodePipelineのサービスロールからアクセスできること
- CodeBuildのサービスロールからアクセスできること
- CodeCommitアクセス用ロールからアクセスできること
つまり、CodePipeline/CodeBuildで暗号化キーを指定した上で、以下の図のようなアクセス権限を持つようにIAMロールおよびS3バケット、KMS暗号化キーを設定していく必要があります
実際に構築してみる(前半戦:IAMロールやアーティファクト用のバケット、KMSの暗号化キーの設定)
初期状態
最初の状態として、
- ソースコードが開発環境側のCodeCommitのリポジトリで管理されていること
- 本番環境ではECSによってサービスが動作した状態になっていること
を仮定して進めていきます。
CodePipelineのサービスロールの準備(本番環境側のアカウント)
CodePipelineのサービスロールを作成します。今回は以下のようなポリシードキュメントを持つポリシーを作成し、アタッチします。
開発環境のアカウントへのAssumeRoleの権限とCodePipelineで必要なアクセス権限を与えています。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AssumeRolePolicy", "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": [ "arn:aws:iam::<開発環境用AWSアカウントのID>:role/*" ] }, { "Sid": "S3Policy", "Action": [ "s3:PutObject", "s3:GetObject", "s3:GetObjectVersion", "s3:GetBucketVersioning" ], "Resource": "*", "Effect": "Allow" }, { "Sid": "CodeBuildPolicy", "Action": [ "codebuild:BatchGetBuilds", "codebuild:StartBuild" ], "Resource": "*", "Effect": "Allow" }, { "Sid": "ECSPolicy", "Action": [ "ecs:DescribeServices", "ecs:DescribeTaskDefinition", "ecs:DescribeTasks", "ecs:ListTasks", "ecs:RegisterTaskDefinition", "ecs:UpdateService", "iam:PassRole" ], "Resource": "*", "Effect": "Allow" } ] }
CodePipelineがこのロールを使用できる必要があるため、信頼ポリシーは以下のように設定します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "codepipeline.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
CodeBuildのサービスロールの作成(本番環境側のアカウント)
CodeBuild用のサービスロールを作成していきます。今回は以下のようなポリシードキュメントを持つポリシーを作成し、アタッチします。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "CloudWatchLogsPolicy", "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": [ "*" ] }, { "Sid": "S3ObjectPolicy", "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:GetObjectVersion" ], "Resource": [ "*" ] }, { "Sid": "ECRPowerUserPolicy", "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:GetRepositoryPolicy", "ecr:DescribeRepositories", "ecr:ListImages", "ecr:DescribeImages", "ecr:BatchGetImage", "ecr:InitiateLayerUpload", "ecr:UploadLayerPart", "ecr:CompleteLayerUpload", "ecr:PutImage" ], "Resource": "*" } ] }
CodeBuildがこのロールを使用できる必要があるため、信頼ポリシーは以下のように設定します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "codebuild.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
アーティファクト用S3バケットの準備(本番環境側のアカウント)
アーティファクトを受け渡すためのバケットにアカウントをまたいだアクセスができるようにしていきます。
作成したアーティファクトバケットのバケットポリシーを以下のように編集し、開発環境アカウントのrootへのアクセスを許可します。 CodeBuildおよびCodePipelineのサービスロールに対しては、それぞれのポリシーでバケットへのアクセスが許可されているため、ここではバケットポリシーでの明示的な許可は行っていません。
{ "Version": "2012-10-17", "Id": "SSEAndSSLPolicy", "Statement": [ { "Sid": "DenyUnEncryptedObjectUploads", "Effect": "Deny", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::<アーティファクトバケットの名前>/*", "Condition": { "StringNotEquals": { "s3:x-amz-server-side-encryption": "aws:kms" } } }, { "Sid": "DenyInsecureConnections", "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": "arn:aws:s3:::<アーティファクトバケットの名前>/*", "Condition": { "Bool": { "aws:SecureTransport": false } } }, { "Sid": "CrossAccountS3GetPutPolicy", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::<開発環境アカウントのID>:root" }, "Action": [ "s3:Get*", "s3:Put*" ], "Resource": "arn:aws:s3:::<アーティファクトバケットの名前>/*" }, { "Sid": "CrossAccountS3ListPolicy", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::<開発環境アカウントのID>:root" }, "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::<アーティファクトバケットの名前>" } ] }
暗号化キーの作成(本番環境側のアカウント)
アカウントをまたいでアーティファクトを受け渡すためにアーティファクトの暗号化キーにアカウントをまたいだアクセスができるようにしていきます。
ここで気をつけないといけないのは、パイプラインを作成するリージョンと同じリージョンで暗号化キーを作成する必要があることです。
作成した暗号化キーのキーポリシーは、CodeBuildとCodePipelineのサービスロールからおよび開発環境からのクロスアカウントでのアクセスを許可するため、以下のように設定します。
{ "Version": "2012-10-17", "Id": "key-policy-name", "Statement": [ { "Sid": "Enable IAM User Permissions", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::<本番環境アカウントのID>:root" }, "Action": "kms:*", "Resource": "*" }, { "Sid": "Allow access for Key Administrators", "Effect": "Allow", "Principal": { "AWS": "<KMSの暗号化キーの管理ユーザのARN>" }, "Action": [ "kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*", "kms:Update*", "kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*", "kms:TagResource", "kms:UntagResource", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion" ], "Resource": "*" }, { "Sid": "Allow use of the key", "Effect": "Allow", "Principal": { "AWS": [ "<CodeBuildのサービスロールのARN>", "<CodePipelineのサービスロールのARN>", "arn:aws:iam::<開発環境のAWSアカウントID>:root" ] }, "Action": [ "kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey" ], "Resource": "*" }, { "Sid": "Allow attachment of persistent resources", "Effect": "Allow", "Principal": { "AWS": [ "<CodeBuildのサービスロールのARN>", "<CodePipelineのサービスロールのARN>", "arn:aws:iam::<開発環境のAWSアカウントID>:root" ] }, "Action": [ "kms:CreateGrant", "kms:ListGrants", "kms:RevokeGrant" ], "Resource": "*", "Condition": { "Bool": { "kms:GrantIsForAWSResource": "true" } } } ] }
CodeCommitアクセス用ロールの作成(開発環境側アカウント)
CodeCommitアクセス用ロールにアーティファクトバケットおよび暗号化キーへのアクセスを許可します。
そして、アーティファクトバケットおよび暗号化キーへのクロスアカウントアクセスを許可するポリシーを作成、アタッチしロールを作成します。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "UploadArtifactPolicy", "Effect": "Allow", "Action": [ "s3:PutObject", "s3:PutObjectAcl" ], "Resource": [ "<アーティファクトバケットのARN>/*" ] }, { "Sid": "KMSAccessPolicy" "Effect": "Allow", "Action": [ "kms:DescribeKey", "kms:GenerateDataKey*", "kms:Encrypt", "kms:ReEncrypt*", "kms:Decrypt" ], "Resource": [ "<暗号化キーのARN>" ] }, { "Sid": "CodeCommitAccessPolicy", "Effect": "Allow", "Action": [ "codecommit:GetBranch", "codecommit:GetCommit", "codecommit:UploadArchive", "codecommit:GetUploadArchiveStatus", "codecommit:CancelUploadArchive" ], "Resource": [ "<CodeCommitリポジトリのARN>" ] } ] }
クロスアカウントでAssumeRoleできる必要があるため、信頼ポリシーは以下のように設定します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::<アカウントID>:root" }, "Action": "sts:AssumeRole", "Condition": {} } ] }
ここまででIAM Roleなどの準備は完了です。
実際に構築してみる(後半戦:パイプラインの構築)
長い準備が終わったので、実際にパイプラインを構築していきましょう。
先にCodeBuildのプロジェクトを作成します。
CodeBuildのプロジェクトの作成
CodePipelineのコンソールからでない場合、コンソールからではソースがCodePipelineであるCodeBuildのプロジェクトを作成できないため、CodeBuildのプロジェクトをAWS CLIから作成していきます。
以下のような内容のJsonファイルをcodebuild.json
のような名前で保存します。
{ "name": "cross-account-build-project", "description": "cross account build project", "source": { "type": "CODEPIPELINE", "buildspec": "buildspec.yml" }, "artifacts": { "type": "CODEPIPELINE" }, "environment": { ## 省略 ビルドの処理に応じた内容にしてください }, "serviceRole": "<CodeBuildサービスロールのARN>", "encryptionKey": "<KMS暗号化キーのARN>" ## 暗号化キーを指定 }
そして、以下のコマンドを実行することでCodeBuildのプロジェクトが作成できます。
$ aws codebuild create-project --cli-input-json file://codebuild.json
CodePipelineのパイプライン作成
最後に、CodePipelineでパイプラインを作成します。 コンソール上からでは、アクションごとにロールを指定できないため、AWS CLIからパイプラインを作成します。
まず、以下のようなファイルをcodepipeline.json
のような名前で作成します
{ "pipeline": { "name": "cross-account-pipeline", "roleArn": "<CodePipelineのサービスロールのアカウント名>", "artifactStore": { "type": "S3", "location": "<アーティファクトバケットの名前>", "encryptionKey": { "id": "<KMSの暗号化キーのARN>", ## 暗号化キーを指定 "type": "KMS" } }, "stages": [ { "name": "Source", "actions": [ { "name": "Source", "actionTypeId": { "category": "Source", "owner": "AWS", "provider": "CodeCommit", "version": "1" }, "runOrder": 1, "configuration": { "RepositoryName": "<CodeCommitのリポジトリ名>", "BranchName": "<処理を実行する対象のブランチ名>" }, "outputArtifacts": [ { "name": "SourceCode" } ], ## CodeCommitのアクションに開発環境にあるCodeCommitアクセス用ロールを指定 "roleArn": "<CodeCommitアクセス用ロールのArn>" } ] }, { "name": "Build", "actions": [ { "name": "Build", "actionTypeId": { "category": "Build", "owner": "AWS", "provider": "CodeBuild", "version": "1" }, "runOrder": 1, "configuration": { "ProjectName": "<CodeBuildのプロジェクト名>" }, "inputArtifacts": [ { "name": "SourceCode" } ], "outputArtifacts": [ { "name": "ImageDefinitions" } ] } ] }, { "name": "Deploy", "actions": [ { "name": "Deploy", "actionTypeId": { "category": "Deploy", "owner": "AWS", "provider": "ECS", "version": "1" }, "runOrder": 1, "configuration": { "ClusterName": "<ECSのクラスタ名>", "ServiceName": "<ECSのサービス名>", "FileName": "<CodeBuildでイメージ定義を書き込んだファイル名>" }, "inputArtifacts": [ { "name": "ImageDefinitions" } ] } ] } ], "version": 1 } }
作成したJsonファイルを使用してパイプラインを作成します
$ aws codepipeline update-pipeline --cli-input-json file://codepipeline.json
しばらくするとCodePipelineが別アカウントのCodeCommitリポジトリからソースコードを取得し、デプロイまでが正しく実行されたことがコンソール上で確認できました!!!
まとめ
複数のアカウントにまたがったパイプラインが構築できると実現できる構成のパターンが大きく広がります。しかし、ご覧頂いたように設定作業が煩雑になりますので用法用量を守って使っていきましょう。